﻿
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace UdpAsyncEcho
{
    sealed class Server : IDisposable
    {
        // *************************************************************** //
        #region [ member fields ]

        private readonly Socket m_socket;
        private readonly SocketAsyncEventArgs m_asyncEvtArgs;

        private byte[] m_recvBuffer;

        private IPEndPoint m_remoteEndpoint;// for reuse

        #endregion

        // *************************************************************** //
        #region [ constructor ]

        public Server(int port)
        {
            m_socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            m_socket.Bind(new IPEndPoint(IPAddress.Any, port));

            m_asyncEvtArgs = new SocketAsyncEventArgs();
            m_asyncEvtArgs.Completed += this.OnIoCompleted;

            m_recvBuffer = new byte[1024];

            m_remoteEndpoint = new IPEndPoint(IPAddress.Any, 0);
        }

        #endregion

        // *************************************************************** //
        #region [ destructor ]

        private bool m_disposed;

        ~Server()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (m_disposed)
                return;

            try
            {
                if (disposing)
                {
                    m_socket.Close();
                    m_asyncEvtArgs.Dispose();
                }
            }
            finally
            {
                m_disposed = true;
            }
        }

        #endregion

        // *************************************************************** //
        #region [ public APIs ]

        public void StartReceive()
        {
            m_socket.StartReceive(m_asyncEvtArgs, m_remoteEndpoint, m_recvBuffer, ProcessReceived);
        }

        #endregion

        // *************************************************************** //
        #region [ private helpers ]

        private void ProcessReceived(SocketAsyncEventArgs evtargs)
        {
            if (evtargs.SocketError == SocketError.Success)
            {
                IPEndPoint srcEndpoint = (IPEndPoint)evtargs.RemoteEndPoint;
                string recvMsg = Encoding.ASCII.GetString(evtargs.Buffer, evtargs.Offset, evtargs.BytesTransferred);
                Console.WriteLine("received from [{0}]: {1}", srcEndpoint, recvMsg);

                byte[] reply = Encoding.ASCII.GetBytes(string.Format("echoback.{0}", recvMsg));
                m_socket.StartSend(m_asyncEvtArgs, srcEndpoint, reply, ProcessSent);
            }
            else
            {
                Console.WriteLine("receive error={0}", new SocketException((int)evtargs.SocketError).Message);
                Dispose();
            }
        }

        private void ProcessSent(SocketAsyncEventArgs evtargs)
        {
            if (evtargs.SocketError == SocketError.Success)
            {
                StartReceive();
            }
            else
            {
                Console.WriteLine("send error={0}", new SocketException((int)evtargs.SocketError).Message);
                Dispose();
            }
        }

        private void OnIoCompleted(object sender, SocketAsyncEventArgs evtargs)
        {
            switch (evtargs.LastOperation)
            {
                case SocketAsyncOperation.ReceiveFrom:
                    ProcessReceived(evtargs);
                    break;

                case SocketAsyncOperation.SendTo:
                    ProcessSent(evtargs);
                    break;

                default:
                    throw new ArgumentOutOfRangeException("unknown socket operation");
            }
        }

        #endregion
    }
}
